记得要复习前面的: CoreJava_day06
CoreJava_day07
类中的属性和方法,可以使用下面四种修饰符进行访问控制:
public > protected > default > private
public :公共的,在所有的地方都可以访问protected:受保护的,当前类中,子类中,同一个包中其他类可以访问default :默认的,当前类中,同一个包中的子类中可以访问 注意:default默认的,指的是空修饰符,并不是default这个关键字 例如,String name; 在类中,这种情况就是默认的修饰符private:私有的,当前类可以访问 修饰符类中同包非子类同包子类不同包子类同包子类public✔✔✔✔✔protected✔✔✔✔✘default✔✔✔✘✘private✔✘✘✘✘同一个类中,全部可以访问 同包非子类中,四个修饰符除了private,都可以访问。 同包子类中,四个修饰符除了private,都可以访问。
在另一个包中,创建类进行访问测试:
package com.lz.zm.six; import com.lz.day04.Test; public class OtherTest { //在不同包的非子类中访问 public void test() { //需要创建对象,然后访问 Test test = new Test(); System.out.println(test.pu); // 编译报错 // System.out.println(test.pro); // System.out.println(test.de); // System.out.println(test.pri); } } class otherSub extends Test { //在不同包的子类中访问 public void test(){ // 继承后可以直接访问 System.out.println(pro); System.out.println(pu); // 编译报错 // System.out.println(pri); // System.out.println(de); } }在不同包非子类中,只有public可以访问 在不同包子类中,只有public和protected可以访问
思考,正常情况下,编写一个类,都可以使用哪些权限控制修饰符?正常编写的类,可以使用两种权限控制修饰符: 例如:
public class A {} class B extends A {}但是,内部类可以使用四种限权控制修饰符:
public class Test { private class A {} class B {} protected class C {} public class D {} }在Test类的内部,嵌套了四个内部类:A B C D,分别使用了private、default、protected、public进行了修饰
在类中在定义一个类,称为内部类
非内部类:
public class Test { } class A { }内部类:
public class Test { // 外部类 public class A { // 内部类 } }内部类一共分为四种形式:
成员内部类静态内部类局部内部类匿名内部类在类中可以定义成员变量,成员方法,这样我们也可以定义成员内部类
// 外部类 public class MemberOuterClass { // 外部类属性 private String name; private static int age; // 外部类方法 public void run() { } public static void go() { } /* * 成员内部类,声明开始 */ public class MemberInnerClass { // 内部类属性 private String name; private int age; // private static int age; // 内部类方法 public void run() { } /* * public static void go() { * * } */ }// 声明结束 }注意,在内部类中,不能写静态成员变量和静态方法 注意,当前这个代码,编译成功后,会生成俩个class文件,一个对应外部类,一个对应内部类
编译生成的2个class文件的名字分别为: MemberOuterClass.class MemberOuterClass$MemberInnerClass.class
成员内部类和外部类的相互访问
成员内部类访问外部类的属性和方法 // 外部类 public class MemberOuterClass { // 外部类属性 private String name; private static int age; // 外部类方法 public void run() { } public static void go() { } /* * 成员内部类,声明开始 */ public class MemberInnerClass { // 内部类属性 private String name; private int age; // private static int age; // 内部类方法 public void run(String name) { System.out.println(name); System.out.println(this.name); // 访问外部类非静态属性 System.out.println(MemberOuterClass.this.name); // 访问外部类静态属性 System.out.println(MemberOuterClass.age); // 外部类静态方法 MemberOuterClass.go(); // 外部类非静态方法 MemberOuterClass.this.run(); } /* * public static void go() { * * } */ }// 声明结束 } 外部类访问成员内部类的属性和方法 // 外部类 public class MemberOuterClass { // 外部类属性 private String name; private static int age; // 外部类方法 public void run() { //需要创建内部类对象,然后才可以访问 MemberInnerClass t = new MemberInnerClass(); // 访问内部类成员变量 System.out.println(t.age); System.out.println(t.name); t.run("李白"); } public static void go() { } /* * 成员内部类,声明开始 */ public class MemberInnerClass { // 内部类属性 private String name; private int age; // private static int age; // 内部类方法 public void run(String name) { System.out.println(name); System.out.println(this.name); // 访问外部类非静态属性 System.out.println(MemberOuterClass.this.name); // 访问外部类静态属性 System.out.println(MemberOuterClass.age); // 外部类静态方法 MemberOuterClass.go(); // 外部类非静态方法 MemberOuterClass.this.run(); } /* * public static void go() { * * } */ }// 声明结束 }其他类中使用这个内部类
import com.lyy.zm.six.MemberOuterClass.MemberInnerClass; public class OutAndInnerClassTest { public static void main(String[] args) { MemberOuterClass m = new MemberOuterClass(); MemberInnerClass i = m.new MemberInnerClass(); i.run("小黑"); } }在其他类中,使用这个非private修饰的成员内部类的时候,需要注意以下几点:
这个内部类需要import导入,并且是外部类.内部类的形式导入在创建对象的时候,需要先创建出外部类对象,然后使用外部类对象再创建内部类对象 形式为:外部类对象.new 内部类对象 成员内部类私有的话,其他类无法创建内部类对象,但是在外部类可以创建内部类 内部类访问外部类静态成员:直接通过外部类.成员思考,类的内部除了嵌套另一个类之外,是否还可以嵌套接口?
可以,不仅类中可以嵌套接口,接口内部也可以嵌套其他接口 比如,参考java.util.Map接口中的内部接口Entry
思考,什么情况下会使用内部类?
在对事物进行抽象的时候,若一个事物内部还包含其他事物,就可以考虑使用内部类这种结构。
静态内部类可以有静态属性和静态方法 生成的class文件: OutClass.class OutClass$staticInerClass.class
静态内部类和外部类的相互访问
静态内部类访问外部类的属性和方法 public class OutClass { private String mame; private static int age; public void run() { } public static void go() { } // 静态内部类 public static class staticInerClass { private String name; // 静态属性 private static int age; public void run(String name) { System.out.println(name); System.out.println(this.name); System.out.println(age); //静态内部类中,无法访问外部类的非静态属性和方法 // System.out.println(OutClass.this.mame); // OutClass.this.run(); //静态内部类中,访问外部类的非静态属性和方法 OutClass outClass = new OutClass(); System.out.println(outClass.mame); outClass.run(); //访问外部类的静态属性和方法 System.out.println(OutClass.age); OutClass.go(); } // 静态方法 public static void go() { } } }注意,在静态内部类中访问不了静态属性和方法,可以创建外部类对象,然后去访问
外部类访问静态内部类的属性和方法 public class OutClass { private String mame; private static int age; public void run() { // 外部类访问静态内部类中的静态属性和静态方法 System.out.println(staticInerClass.age); staticInerClass.go(); // 外部类访问静态内部类中的非静态属性和方法 //需要创建内部类对象,然后才可以访问 staticInerClass c = new staticInerClass(); System.out.println(c.name); c.run("tom"); } public static void go() { } // 静态内部类 public static class staticInerClass { private String name; // 静态属性 private static int age; public void run(String name) { System.out.println(name); System.out.println(this.name); System.out.println(age); //静态内部类中,无法访问外部类的非静态属性和方法 // System.out.println(OutClass.this.mame); // OutClass.this.run(); //静态内部类中,访问外部类的非静态属性和方法 OutClass outClass = new OutClass(); System.out.println(outClass.mame); outClass.run(); //访问外部类的静态属性和方法 System.out.println(OutClass.age); OutClass.go(); } // 静态方法 public static void go() { } } }外部类访问内部类的非静态属性和方法:创建对象
其他类中使用这个内部类: 如果这个静态内部类不是private修饰的,那么在其他类中就可以访问到这个内部类
import com.lz.zm.six.OutClass.staticInerClass; public class OutAndInnerClassTest { public static void main(String[] args) { staticInerClass sInerClass = new staticInerClass(); sInerClass.run("李白"); } }在其他类中,使用这个非private修饰的静态内部类的时候,需要注意以下几点:
这个内部类需要import导入,并且是外部类.内部类的形式导入在创建对象的时候,直接使用这个静态内部类的名字即可new 静态内部类对象();,不再需要依赖外部类对象 ==表示要比较这个两个对象的内存地址是否相等局部内部类,是另一种形式的内部,声明在外部类的方法中,相当于方法中的局部变量的位置,它的作用范围只是在当前方法中。
局部内部类是最不常用的一种内部类
public class LocalOuterClass { public void run(String name) { // 局部内部类 class LocalInnerClass { } } }局部内部类和外部类的相互访问:
局部内部类访问外部类属性和方法
public class LocalOuterClass { // 外部类的属性 private String name; private static int age; // 外部类方法 public void run() { } public static void go() { } public void run(String name) { // 局部内部类 class LocalInnerClass { // 局部内部类的属性 private String name; // 无法定义静态属性和静态方法 // private static int age; /* * public static void go() { * * } */ public void run(String name) { // 当前方法里面的参数name System.out.println(name); // 访问内部类自己的属性 System.out.println(this.name); // 外部类的静态属性 System.out.println(LocalOuterClass.age); // 外部类的非静态属性 System.out.println(LocalOuterClass.this.name); // 外部类静态方法 LocalOuterClass.go(); // 外部类非静态方法 LocalOuterClass.this.run(); } } } }局部内部类不能有静态属性和静态方法 可以直接外部类名.属性或者方法调用,静态的话加个this
外部类访问局部内部类的属性和方法
public class LocalOuterClass { // 外部类的属性 private String name; private static int age; // 外部类方法 public void run() { } public static void go() { } public void run(String name) { // 局部内部类 class LocalInnerClass { // 局部内部类的属性 private String name; // 无法定义静态属性和静态方法 // private static int age; /* * public static void go() { * * } */ public void run(String name) { // 当前方法里面的参数name System.out.println(name); // 访问内部类自己的属性 System.out.println(this.name); // 外部类的静态属性 System.out.println(LocalOuterClass.age); // 外部类的非静态属性 System.out.println(LocalOuterClass.this.name); // 外部类静态方法 LocalOuterClass.go(); // 外部类非静态方法 LocalOuterClass.this.run(); } } // 外部类访问局部内部类(在同一方法中) LocalInnerClass localInnerClass = new LocalInnerClass(); // 局部内部类的非静态属性(局部内部类中只有非静态) System.out.println(localInnerClass.name); // 调用访问 localInnerClass.run("tom"); } }局部内部类,只能在当前声明的方法中进行使用。在局部内部类的下面去创建对象
局部内部类中,访问当前方法中的变量,这个变量必须是final修饰的
public void sayHello(final String name) { final int num = 1; // 局部内部类 class A { public void test() { System.out.println(name); System.out.println(num); } } }final修饰的变量,只能赋值一次 在JDK1.8中,一个局部变量在局部内部类中进行方法了,那么这个局部变量自动变为final修饰
匿名内部类是一种没有名字的内部类,是一种内部类的简化写法
在普通的代码中,使用一个接口的步骤如下:
声明一个类,去实现这个接口实现这个接口中的抽象方法(重写)在其他代码中,创建这个类的对象调用类中实现(重写)红藕的方法这个过程就是把接口的抽象方法重写了,最后再调用重写的方法 那么,匿名内部类,就是把这个过程简化了,可以更加方便的实现(重写)后的方法 格式:
父类或者接口类型 变量名 = new 父类或者接口() { // 方法重写 public void method { // 执行语句 } }; // 调用实现(重写)后的方法 变量名.method();new 创建对象 new Person():创建Person类型对象
匿名内部类的两种形式:
利用一个父类,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个父类的子类型利用一个接口,进行声明并创建匿名内部类对象,这个匿名内部类默认就是这个接口的实现类匿名内部类由于没有类名:
匿名内部类必须依托于一个父类型(抽象类)或者一个接口匿名内部类在声明的同时,就必须创建出对象,否则后面就没法创建了匿名内部类中无法定义构造器利用父类型来声明并创建匿名内部类对象
public abstract class Animal { public abstract void run(); } class Test { public static void main(String[] args) { /** * 创建一个没有名字的类,继承Anaimal抽象类,同时重写抽象类方法 * 并且创建这个类的对象 */ Animal animal = new Animal() { @Override public void run() { System.out.println("这是匿名内部类中的默认实现"); } }; animal.run(); } }注意,利用父类型来声明这个匿名内部类,那么这个匿名内部类默认就是这个父类型的子类
利用接口来声明并创建匿名内部类对象
public interface Action { void run(); } class Test1 { public static void main(String[] args) { Action a = new Action() { @Override public void run() { System.out.println("匿名内部类中的默认实现"); } }; a.run(); } }案例:
import java.util.Arrays; public interface Algorithm { void sort(int[] arr); } class Test2 { // 使用指定算法,对数组arr进行排序 public void sort(int[] arr,Algorithm algorithm) { algorithm.sort(arr); } public static void main(String[] args) { Test2 t = new Test2(); int[] arr = {3,4,1,23,2,9}; Algorithm algorithm = new Algorithm() { @Override public void sort(int[] arr) { //使用当前需要的排序算法 //例如,这里简单的使用Arrays工具类中的排序方法 Arrays.sort(arr); } }; t.sort(arr, algorithm); System.out.println(Arrays.toString(arr)); } }运行结果: [1, 2, 3, 4, 9, 23]
内部类的选择:
假设现在已经确定了要使用内部类,那么一般情况下,该如何选择?
考虑这个内部类,如果需要反复的进行多次使用(必须有名字) 在这个内部类中,如果需要定义静态的属性和方法,选择使用静态内部类在这个内部类中,如果需要访问外部类的非静态属性和方法,选择使用成员内部类 考虑这个内部类,如果只需要使用一次(可以没有名字) 选择使用匿名内部类 局部内部类,几乎不会使用Object是祖宗类,超父类
搜源码:ctrl+shift+t该方法可以返回一个对象默认的字符串形式:
public class Object{ public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } }toHexString:转十六进制
package com.lz.zm.serven; public class Student { @Override public String toString() { return "哈哈"; } } package com.lz.zm.serven; public class StudentTest { public static void main(String[] args) { Student student = new Student(); //com.lz.zm.serven.Student@6d06d69c System.out.println(student.toString()); // 手动调用toString // 如果不想要这样写,可以重新toString方法 // 重写后输出:哈哈 System.out.println(student); // 默认调用toString(推荐使用) } }该方法是非常重要的一种方法,它可以返回一个引用在运行时所指向的对象,具体类型是什么 该方法是native修饰的本地方法,不是java语言实现的。
public class Object{ public final native Class<?> getClass(); }native :本地方法,其他语言写的(不是Java) final :不能重写
package com.lz.zm.serven; public class StudentTest { public static void main(String[] args) { Student student = new Student(); // class com.lz.zm.serven.Student System.out.println(student.getClass()); } } package com.lz.zm.serven; public class StudentTest { public static void show(Object obj) { System.out.println(obj.getClass()); } public static void main(String[] args) { Student student = new Student(); show(student); // class com.lz.zm.serven.Student show(new Object()); //class java.lang.Object } }不是本地方法
Person p1 = new Person(); Person p2 = new Person(); /** * == 如果用来判断引用类型那就是比较地址 */ // false System.out.println(p1.equals(p2)); Person p1 = new Person(1,"李四"); Person p2 = new Person(1,"李四"); // false System.out.println(p1.equals(p2)); /** * 如果id,name都一样,就认为是同一个人 * 此时equals不满足条件了,需要重写 */对equals方法重写,一般需要注意以下几点(约定):
自反性:对任意引用obj,obj.equals(obj)的返回值一定为true.对称性:对于任何引用o1、o2,当且仅当o1.equals(o2)返回值为true时,o2.equals(o1)的返回值一定为true;传递性:如果o1.equals(o2)为true, o2.equals(o3)为true,则o1.equals(o3)也一定为true一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变非空性:任何非空的引用obj,obj.equals(null)的返回值一定为false Person p1 = new Person(1,"李四"); Person p2 = new Person(1,"李四"); System.out.println(p1.equals(p1)); //自反性 true重写equals:
package corejave_day01.Object; public class Person { private int id; private String name; public Person(int id, String name) { this.id = id; this.name = name; } public Person() {} 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; } @Override public String toString() { return "id=" + id + ", name=" + name ; } /** * 如果equals(null) 一定返回false :非空性 * 自反性: */ @Override public boolean equals(Object obj) { // 非空性 if (obj == null) { return false; } // 自反性 (当前对象 == 传进来的对象) 两者地址一样 if (this == obj) { return true; } // 两者类型不一样 if (!(obj instanceof Person)) { return false; } // obj转成Student Person p1 = (Person)obj; // 内容一样返回true if (this.id == p1.id) { return true; } // 如果全都不满足 return false; } }运行结果:true
Person p1 = new Person(1,"李四"); Person p2 = new Person(1,"李四"); // true System.out.println(p1.equals(p2)); -------------------------------------------------------- Person p1 = new Person(1,"李四"); Person p2 = new Person(1,"李四"); Person p3 = new Person(1,"张三"); // true 因为之定义了id相同 System.out.println(p1.equals(p3));判断两个对象是否相等
== 比较的是对象的地址equals 按照自己的规则去判断两个对象 String a = new String("1"); String b = new String("1"); System.out.println(a); // 1 String重写了toString方法 System.out.println(b); // 1 System.out.println(a == b); // false System.out.println(a.equals(b)); // true String重写了equals方法修改下面的代码:
// 内容一样返回true if (this.id == p1.id && this.name.equals(p1.name)) { return true; } Person p1 = new Person(1,"李四"); Person p2 = new Person(1,"李四"); Person p3 = new Person(1,"张三"); System.out.println(p2.equals(p3)); // 修改前:true 修改后:falseHash,一般翻译做“散列”,也可以音译为“哈希”,就是把任意长度的数据输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。 . 一个任意长度的输入转为一个固定长度的输出,是一种压缩映射,也就是说,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。
返回一个对象的内存地址(int类型数),可以通过任意数据然后经过哈希算法得到一个hash值两个对象如果相同那么其hashcode相同,但是如果不同,那么两个对象一定不同;如果相同,那么这两个对象不一定统一对象 public class Object{ public native int hashCode(); }本地方法
System.out.println(p1.hashCode()); // 1829164700 // 可以转成十六进制 System.out.println(Integer.toHexString(p1.hashCode())); // 6d06d69c对于俩个对象的hashCode值:
相等的两个对象,hashCode值一定相等hashCode值相同,两个个对象有可能相等,也可能不同等hashCode值不同,两个对象一定不同思考,hashCode在代码中有什么用?
现在有100个对象,突然又来了一个对象obj,那么怎么判断这个obj对象和之前的100个对象是否有相等的呢?
除了使用obj对象的equals方法和之前的100个对象比较100次之外,是否还有更好的方式?
使用hashCode()
Integer integer = new Integer(1); Integer i4 = Integer.valueOf(1); Integer i3 = 1; String s = "1"; System.out.println(integer.hashCode()); // 1 System.out.println(i3.hashCode()); // 1 System.out.println(i4.hashCode()); // 1 System.out.println(s.hashCode()); // 49 System.out.println(integer == i3); // false 不是同一个对象 System.out.println(i3==i4); // true字符串String,是程序中使用最多的一种数据,JVM在内存中专门设置了一块区域(字符串常量池),来提高字字符串对象的使用效率。 JDK1.7字符串常量池存在堆区中
创建字符串对象,和其他普通对象一样,会占用计算机的资源(时间和空间),作为最常用的数据类型,大量频繁的创建字符串对象,会极大程度地影响程序的性能。
JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
为字符串开辟一个字符串常量池,类似于缓存区创建字符串常量时,首先会检查字符串常量池中是否存在该字符串,如果存在该字符串,则返回该实例的引用,如果不存在,则实例化创建该字符串,并放入池中1.第一种情况: 2.第二种情况:
String a = "11"; String b = new String("11"); System.out.println(a==b); // false System.out.println(a.equals(b)); // true 比较内容运行结果: helloworld helloworld false