面向特性3大特性
1)封装性
封装性有2个含义:
-
整体性:把属性和行为看成是一个密不可分的整体,将这两者封装在一起,即封装成对象
-
信息隐蔽性:把不需要外界知道的信息隐藏起来,即对象中的属性和行为仅允许外界用户知道或使用,但不允许修改,权限修饰符
private
2)继承性
被继承的类称为基类、父类
通过继承产生的类称为派生类、子类
注意,java中单继承,多实现
3)多态性
多态分为2种:
-
方法重载:在一个类中,允许存在多个方法使用相同的方法名,但方法的参数不同,方法体不同
-
对象多态:子类对象可以与父类对象互相转换,根据其使用的子类不同,完成的功能也不同
类与对象的关系
类是对象的模板,对象是类的实现
属性即类中的变量(也称为成员变量),行为即类中的方法
对象的创建
因为类和数组都属于引用类型,创建对象分为声明对象和实例化对象两步
Person p = null;// 声明对象,在栈内存开辟空间
p = new Person();// 实例化对象,在堆内存开辟空间,并将堆引用传递给栈空间中
只要是引用类型,创建都可以使用如上的定义方式(引用类型:数组、String、类、接口、枚举、注解)
栈内存中存储的是对象名称,即对应的堆内存的引用地址
堆内存中存储的是对象的属性信息,且引用数据都有默认值,为null
注意,对象要使用前必须实例化,没有new实例化的对象调用属性和方法时直接报null
被new实例化的对象称为实例化对象
创建多个对象时,会在栈堆内存中开辟多个内存空间
Person p1 = new Person("a", 22);
Person p2 = new Person("b", 23);
对象的引用传递
public static void referenceTrans(){
Person p1 = new Person("xu", 25);
Person p2 = p1;
p2.setName("song");
p2.setAge(26);
System.out.println(p1.toString());// song 26
}
所谓的对象引用传递,实际上就是将堆内存空间的使用权交给多个栈空间,每个栈内存空间都可以去修改堆内存的内容
如上代码,将p1的堆内存地址使用权交给p2,p2调用set方法修改的就是p1所指向的堆内存中的属性,故打印是被修改的属性
Java提供垃圾回收机制GC,会不定期的释放不用的内存空间,只要对象不使用,就会等待GC释放空间
一个栈内存空间只能指向一个堆内存空间,如果要切换,则必须先断开已有的指向才能分配新的空间
封装性
使用private权限修饰符,封装类中的属性为私有,不能由对象直接进行访问,从而保证对入口有所限制
只要是被封装的属性,则必须通过setter和getter方法设置和获取
如果想要对设置的值做判断,只需要在setter()方法里加判断代码即可
在之后的开发中一定要明确,类中的全部属性都必须封装,即使用private修饰,通过getter和setter获取设置
面向对象中的封装有2个含义,见上,并不单指private信息隐蔽性,还有整体性
class Person{
// 成员变量
private String name;
private int age;
// 成员方法
public void talk(){
System.out.println("Hello, World !");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 构造方法
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
// getter和setter
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
if(this.age > 0){
this.age = age;
}
}
}
// 封装,通过getter、setter获取设置属性
public static void getAndSetPerson(){
Person person = new Person("ming", 20);
System.out.println(person.getName() + person.getAge());
person.setName("hong");
person.setAge(30);
System.out.println(person.getName() + person.getAge());
}
构造方法
构造方法的主要作用是,为类中的属性初始化
Person person = new Person();
在java中出现()就是调用方法,这里也一样,调用了无参构造方法
class Person{
// 成员变量
private String name;
private int age;
// 构造方法
public Person(){
}
public Person(String name){
this.name = name;
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
1)构造方法的名称必须与类名称一致
2)构造方法的声明不能有任何返回值类型的声明
3)不能在构造方法中使用return语句
4)当调用new实例化对象时,才会调用构造方法
5)只要有类,就必定存在至少一个构造方法
若一个类中没有明确的声明一个构造方法时,则会在编译时直接生成一个无参数的,什么都不会做的构造方法
若一个类中已经存在一个构造方法,则不会生成默认的无参构造方法
6)构造方法可以重载,即多个相同方法名,不同参数类型或参数个数
访问权限
java中一共有4种访问权限
-
public 公共的(所有包所有类可访问)
-
protect 受保护的(非本包类可访问)
-
default 包访问权限(本包的类可访问)
-
private 私有的(仅本类可访问)
匿名对象
匿名对象,就是没有明确给出对象的名称
匿名对象只会开辟堆内存,不会开辟栈内存
class Person{
// 成员变量
private String name;
private int age;
// 成员方法
public void talk(){
System.out.println("Hello, " + this.name);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 构造方法
public Person(){
}
public Person(String name){
this.name = name;
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
// getter和setter
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
// 匿名对象
public static void anonymousObject(String name){
new Person(name).talk();
}
注意,匿名对象一般是一次性使用,因为其没有栈内存引用,此对象使用一次之后就会等待被GC回收
匿名对象实际上就是一个堆内存
String创建字符串的2种方式
String实际上是一个类,属于引用数据类型
String有2种方式实例化
1)双引号创建 String str = "Hello, World";
2)实例化 String str = new String("Hello, World");
双引号创建的方式会用到Java的缓存池机制
实例化的方式实际上是调用String类的构造方法
上面两种方式还是有区别的,主要是在于内存中的存取机制不同
// 当使用双引号定义对象时,Java环境首先去字符串缓冲池寻找相同的字符串
// 如果存在就直接拿出来使用
// 如果不存在则创建一个新的字符串放到缓冲池中
String a1 = "hello";
String a2 = "hello";
// new每次都会在堆内存中开辟一个新的空间
String b1 = new String("hello");
String b2 = new String("hello");
// 双引号
// a1、a2都在缓冲池中指向相同的字符串,引用值相同
System.out.println(a1 == a2);// true
System.out.println(a1.equals(a2));// true
// 构造方法
// b1、b2虽然值内容相等,但是指向不同的堆内存地址
System.out.println(b1 == b2);// false
System.out.println(b1.equals(b2)); // true
// a1、b1属于不同的内存地址,但是值内容相等
System.out.println(a1 == b1);// false
System.out.println(a1.equals(b1));// true
String比较
下面的说法是针对String类型的比较
== 比较的是堆内存的地址
equals() 比较的是堆内存的值内容
public static void main(String[] args) {
String str1 = "Hello";
String str2 = new String("Hello");
String str3 = str2;// 对象的引用传递
// == 比较的是堆内存的地址,new 一次就会有一个新的堆内存空间
System.out.println(str1 == str2);// false
System.out.println(str1 == str3);// false
System.out.println(str2 == str3);// true
// String中的equals()方法经过重写,比较的是堆内存的值内容
System.out.println(str1.equals(str2));// true
System.out.println(str1.equals(str3));// true
System.out.println(str2.equals(str3));// true
}
在Object父类中,equals()也是比较堆内存的地址,看源代码知道其实就是==
public boolean equals(Object obj) {
return (this == obj);
}
但是已经有一个双等号是比较堆地址了,所以String类重写了equals()方法,比较堆内容
字符串String的内容不可改变
使用String类进行操作时,一种重要的特性是,字符串的内容一旦声明就不可改变
public static void test3(){
String str1 = "Hello";
str1 = str1 + "World";
System.out.println(str1);
}
以上的代码,并没有修改String str1的堆内存值内容
而是重新开辟堆内存空间,放置内容Hello World
之前的Hello堆内存空间依旧存在
只不过,str1是通过断开 - 再连接新的内存地址的方式完成的
即本身字符串中的内容并没有任何的改变
实际开发中,应当避免字符串频繁改变,因为这意味着字符串的指向要断开-连接,代码的性能会非常低
频繁改变字符串可以使用StringBuffer来实现,会在后续介绍
String类常用方法
public String(char[] value) 将字符数组转换为String(构造方法)
public String(byte[] bytes) 将字节数组转换为String(构造方法)
public char[] toCharArray() 将一个字符串转换为字符数组
public char charAt(int index) 从一个字符串中取出指定位置的字符(从0开始)
public byte[] getBytes() 将一个字符串变为bytes数组
public int length() 字符串长度
public int indexOf(String str) 从头开始查找指定的字符串位置(从0开始)
public String trim() 去掉字符串左右两端的空格
public String subString(int beginIndex) 从指定位置开始,一直取到尾进行字符串截取
public String subString(int begin, int end) 从指定位置开始截取字符串(包头不包尾)
public String[] split(String regex) 按指定字符对字符串进行拆分,拆分的数据以字符串数组形式返回
public String toUpperCase() 转为全大写
public String toLowerCase() 转为全小写
public boolean startsWith(String prefix) 判断字符串是否以特定字符开头
public boolean endsWith(String suffix) 判断字符串是否以特定字符结尾
public boolean equals(String str) 判断两个字符串值是否相等(String的equals方法经过重写,比较堆内存的值内容)
public boolean equalsIgnoreCase(String str) 不区分大小写比较两个字符串值
public String replaceAll(String regex, String replacement) 字符串替换
引用传递
引用传递,就是将堆内存地址的使用权交给多个栈内存空间
案例一: fun方法接收d,即将d的堆内存地址使用权交给demo
class Demo{
int a = 30;
}
public static void main(String[] args) {
Demo d = new Demo();
d.a = 50;
System.out.println("调用fun方法前,a = " + d.a);// 50
fun(d);// 将d的堆内存地址交给入参demo
System.out.println("调用fun方法后,a = " + d.a);//100
}
public static void fun(Demo demo){
demo.a = 100;
}
案例二:String字符串一旦声明则不可改变,改变的只是堆内存指向
public static void main(String[] args) {
String str1 = "hello";
System.out.println("调用fun2方法前,str1 = " + str1);
fun2(str1);
System.out.println("调用fun2方法后,str1 = " + str1);
}
public static void fun2(String str){
str = "World";
}
案例三:String作为Demo类的一个属性(区别于案例2中直接改变String字符串类)
class Demo3{
String b = "hello";
}
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
demo3.b = "he";
System.out.println("调用fun3方法前,b = " + demo3.b);//he
fun3(demo3);// 将d的堆内存地址交给入参demo
System.out.println("调用fun3方法后,b = " + demo3.b);//world
}
public static void fun3(Demo3 demo3){
demo3.b = "world";
}
this关键字
1)使用this调用本类的属性,见set方法,不会产生歧义
2)使用this调用本类的构造方法,如this()
3)使用this表示当前的对象(最重要),谁调用类方法,this就表示谁
public class Student {
private String id;
private String name;
private float mathScore;
private float englishScore;
private float computerScore;
public Student(){
super();
}
// 构造方法的本意是将参数传递的id值赋值给id属性
// 2、this调用构造方法,如this()调用无参构造
public Student(String id, String name, float mathScore, float englishScore, float computerScore){
this();
this.id = id;
this.name = name;
this.mathScore = mathScore;
this.englishScore = englishScore;
this.computerScore = computerScore;
}
// 使用this比较对象
// 首先比较地址是否相同,若地址相同则肯定是同一个对象
// 如果地址不相同,则将一个个属性进行比较
public boolean compare(Person person){
Person p1 = this;
Person p2 = person;
if(p1 == p2){
return true;
}
// 分别判断每一个属性
if((p1.id == p2.id )&& (p1.name.equals(p2.name))){
return true;
}else {
return false;
}
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public float getMathScore() {
return mathScore;
}
public float getEnglishScore() {
return englishScore;
}
public float getComputerScore() {
return computerScore;
}
// 1、this调用本类中的属性,用在set方法中不会产生歧义
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setMathScore(float mathScore) {
this.mathScore = mathScore;
}
public void setEnglishScore(float englishScore) {
this.englishScore = englishScore;
}
public void setComputerScore(float computerScore) {
this.computerScore = computerScore;
}
}
注意,使用this调用构造方法必须放在构造方法的第一行,因为构造方法优先调用
注意,使用this调用构造方法一定要留一个构造方法作为出口,即程序中至少存在一个构造方法是不使用this调用其他构造方法的
一般构造方法在互相调用时会留一个出口,将无参构造方法作为出口,即无参构造方法中不要去调用其他的构造方法
static关键字
- 修饰属性,类属性,由类名直接调用
- 修饰方法,类方法,由类名直接调用
使用static修饰的属性会被所有对象共享,即公共属性,且可以直接使用类名称调用
public class Person1 {
private int id;
private String name;
static String color;
}
public static void main(String[] args) {
Person1 person1 = new Person1(10, "a");
Person1 person2 = new Person1(20, "b");
// static属性由类名直接调用
// 因为不知道一个类到底有多少个对象
Person1.color = "111";
System.out.println(person1);
System.out.println(person2);
}
注意,static修饰的方法不能调用非static类型的属性或方法,尤其是main方法中只能调用static方法
因为非static的属性都需要等待对象开辟堆栈内存在可以使用,而static类型的对象方法都在全局数据区,在对象未实例化时就可以被类名调用
static属性是所有对象共享的,可以用来统计多少实例化对象
public class Person1 {
static int count;
// 只要有对象实例化,就一定会调用其中的构造方法,将static属性的count自增1
public Person1(){
count++;
System.out.println("产生了" + count + "个对象");
}
}
单例类
1、类中存私有静态的实例化对象
2、封装构造方法,不能产生多实例
3、类中准备对外获取实例化对象的方法
public class Singleton {
private int id;
private String name;
// 私有静态的实例化对象
private static Singleton singleton;
// 私有化构造方法
private Singleton(){
}
public void setId(int id) {this.id = id; }
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
// 将内部 singleton对象传递给外部
// 因为Singleton属性为static,所以get方法也要为static
public static Singleton getSingleton() {
if(singleton != null){
singleton = new Singleton();
}
return singleton;
}
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getSingleton();
Singleton singleton2 = Singleton.getSingleton();
System.out.println(singleton1 == singleton2);//true
}
单例的意义
虽然上面实例化了2个Singleton对象,但是实际上所有对象都只使用到类中内部实例化的对象singleton
也就是说无论外部如何使用,最终结果都只有一个实例化对象存在
在设计模式上称为单例设计模式(比喻,外部对象类似于快捷方式,真正引用地址只有一个)
对象数组
即数组的元素是一组对象,注意数组是引用数据类型,需要先开辟空间
引用数据类型初始化之前,每个对象都是默认值,对象的默认值是null,所以在使用时需要对每个对象分别做实例化
Person[] person = new Person[3];
person[0] = new Person(1, "张三");
person[1] = new Person(2, "李四");
person[2] = new Person(3, "王五");
main方法的入参也是对象数组,又称动态数组
public static void main(String[] args) {}
内部类
在类的内部定义另一个类,如在Outer类的内部定义一个类Inner,此时Inner类就是内部类,而Outer是外部类
内部类的好处是可以方便的访问外部类中的私有属性
用static修饰的内部类,只能访问外部类中static的属性和方法
实例化内部类
public class Outer {
private int id;
private String name;
// 内部类,可以直接使用外部类的成员变量
class Inner{
void printInner(){
System.out.println("Inner: " + id + name);
}
}
void printOuter(){
System.out.println("Outer: " + id + name);
}
}
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.printInner();
}
Java中常用的内存区域
在Java中主要的4块内存区域
1、栈内存空间,保存堆内存空间的引用地址
2、堆内存空间,保存每个对象的具体属性内容
3、全局数据区,保存static类型的属性
4、全局代码区,保存所有方法定义
一个简单系统登录
一个简单的开发模式,先写基础操作类,再写封装类,最后用主方法类进行调用
1、Check类实现用户名和密码的校验
2、Operater类,用于封装Check类的操作,并把Check的信息返回到调用处
3、主方法main,登录入口
public class Check {
private String userName;
private String passWord;
// 此处省略Constructor、set、get方法
public boolean validate(String userName, String passWord){
if(userName.equals("lixinghua") && passWord.equals("mldn")){
return true;
}else {
return false;
}
}
}
public class Operater {
private String info[];// 用户输入
public Operater(String info[]){
this.info = info;
}
// 封装方法一:登录
public boolean login(){
if(info.length != 2){
System.out.println("参数无效");
return false;
}else {
String name = info[0];
String passwd = info[1];
Check check = new Check();
return check.validate(name, passwd);
}
}
// 封装方法二:登出
public void logout(){
}
}
public static void main(String[] args) {
String[] info = {"lixinghua", "mldn"};
String[] info1 = {"lixinghua1", "mldn1"};
String[] info2 = {"lixinghua1", "mldn1", "hah"};
Operater operater = new Operater(info2);
System.out.println(operater.login());
}
问题
匿名对象的作用?
由于我们没有记录堆内存对象的地址值,即没有开辟栈空间,所以只能用一次,再次使用就找不到了
匿名对象的好处就是使用完毕就是垃圾,可以在垃圾回收器空闲时回收,节省内存空间
String两种方式实例化的区别?
见上
==和equals()的区别?
1)基本数据类型
== 是比较值内容,基本数据类型没有equals()方法
2)引用数据类型
== 比较的是堆内存地址
equals() 比较的是值内容
equals方法其实是Object基类中的方法,比较的是堆内存地址,源码就是使用==进行比较
但因为== 已经实现了比较堆内存地址,所以很多类都会重写equals方法,进行值内容比较,如String、Integer
public boolean equals(Object obj) {
return (this == obj);
}
public boolean equals(Object anObject) {
//判断地址值是否相等
if (this == anObject) {
return true;
}
//判断对象类型是否为String
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
//判断长度是否相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
//每一个字符都必须相同
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
当然如果堆内存的地址相同的话,堆内存的值也一定是相同的
构造方法的作用
构造方法是在对象实例化时自动调用的方法
也就是说在类中的所有方法中,构造方法会被优先调用的
所以使用this()调用无参构造方法时,需要放在方法内的第一行,不然会报错
public class Student {
private String id;
private String name;
private float mathScore;
private float englishScore;
private float computerScore;
public Student(){
super();
}
// 构造方法的本意是将参数传递的id值赋值给id属性
public Student(String id, String name, float mathScore, float englishScore, float computerScore){
this();
this.id = id;
this.name = name;
this.mathScore = mathScore;
this.englishScore = englishScore;
this.computerScore = computerScore;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public float getMathScore() {
return mathScore;
}
public float getEnglishScore() {
return englishScore;
}
public float getComputerScore() {
return computerScore;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setMathScore(float mathScore) {
this.mathScore = mathScore;
}
public void setEnglishScore(float englishScore) {
this.englishScore = englishScore;
}
public void setComputerScore(float computerScore) {
this.computerScore = computerScore;
}
}
如何比较两个对象是否相等
1)比较对象的堆内存地址是否相等 ==
2)依次比较对象中每个属性值是否相等
单例模式有什么作用?
下面列举单例使用场景
1)Windows的Task Manager(任务管理器)
就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗?
2)windows的Recycle Bin(回收站)
也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例
3)网站的计数器
一般也是采用单例模式实现,否则难以同步
4)应用程序的日志应用
一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
5)Web应用的配置对象的读取
一般也应用单例模式,这个是由于配置文件是共享的资源
6)数据库连接池的设计一般也是采用单例模式
因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗
7)多线程的线程池的设计一般也是采用单例模式
这是由于线程池要方便对池中的线程进行控制
单例模式的场景一般满足4个条件
1)整个程序的运行只允许有一个实例对象
2)需要频繁实例化然后销毁的对象
3)创建对象时消耗过多的资源,但又常用的对象
4)方便资源相互通信的环境
单例模式的实现
1)构造方法必须私有化
2)实例必须是一个且唯一,必须加上static
3)对外提供该类对象的共有方法也是static属性
单例模式的2种形式
1)懒汉式
懒汉比较懒,只有当调用getInstance的时候,才会去初始化这个单例
public class SingletonLH {
/**
*是否 Lazy 初始化:是
*是否多线程安全:否
*实现难度:易
*描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
*这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*/
private static SingletonLH instance;
private SingletonLH (){}
public static SingletonLH getInstance() {
if (instance == null) {
// 懒汉,获取的时候才会实例化对象
instance = new SingletonLH();
}
return instance;
}
}
2)饿汉式(线程安全)
类一旦加载,就会把单例初始化完成,保证getInstance的时候,单例就已经存在
public class SingletonEH {
/**
*是否 Lazy 初始化:否
*是否多线程安全:是
*实现难度:易
*描述:这种方式比较常用,但容易产生垃圾对象。
*优点:没有加锁,执行效率会提高。
*缺点:类加载时就初始化,浪费内存。
*它基于 classloder 机制避免了多线程的同步问题,
* 不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,
* 在单例模式中大多数都是调用 getInstance 方法,
* 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,
* 这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/
// 饿汉,在获取之前就已经准备好实例化对象
private static SingletonEH instance = new SingletonEH();
private SingletonEH (){}
public static SingletonEH getInstance() {
System.out.println("instance:"+instance);
System.out.println("加载饿汉式....");
return instance;
}
}
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
内部类的作用?使用场景
使用内部类,可以方便的访问外部类中的私有操作
- 静态内部类(静态内部类只能访问外部类中的静态属性和静态方法)
- 成员内部类(最普通的内部类,可以访问外部类中的属性和方法)
- 局部内部类(比如在方法里面定义一个内部类,就是局部内部类,其作用域仅限于该方法)
- 匿名内部类
内部类的作用
1、内部类使得多继承的解决方案变得完整
每个内部类都能独立的继承一个接口,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
内部类可以继承(extends)或实现(implements)其他的类或接口,而不受外部类的影响
2、类的封装性
方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏
当我们将内部类声明为private时,只有外部类可以访问内部类,很好地隐藏了内部类
内部类可以直接访问外部类的字段和方法,即使是用private修饰的,相反的,外部类不能直接访问内部类的成员
内部类的原理
内部类是一个编译时的概念,编译后会生成两个独立的class文件
编译后Outer.Inner被重命名为Outer$Inner,句点(.)被替换成了美元符号($)
public class Outer{
private String outerName = "outer";
class Inner{
private String innerName = "inner";
}
}
Outter$Inner.class
Outer.class
匿名内部类
-
匿名内部类是局部内部类的更深入一步。
-
假如只创建某类的一个对象时,就不必将该类进行命名。
-
匿名内部类的前提是存在一个类或者接口,且匿名内部类是写在方法中的。
-
只针对重写一个方法时使用,需要重写多个方法时不建议使用
下面举个多线程的例子
new Thread(){
public void run(){
System.out.println(getName());
}
}.start();