1、面向对象都有哪些特性以及你对这些特性的理解
四大特性:继承,封装,抽象,多态
继承:继承就像是我们现实生活中的父子关系,儿子可以遗传父亲的一些特性,
在面向对象语言中,就是一个类可以继承另一个类的一些特性,从而可以代码重用。
封装:封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口;
在面向对象语言中,封装特性是由类来体现的,我们将现实生活中的一类实体定义成类,
其中包括属性和行为(在Java中就是方法),就好像人类,可以具有name,sex,age等属性,
同时也具有eat(),sleep()等行为,我们在行为中实现一定的功能,也可操作属性。
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。
抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
所以抽象在面向对象语言是由抽象类来体现的。
所以在语言中体现为抽象类不能实例化。
多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。
简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。
多态性分为编译时的多态性和运行时的多态性。
2、抽象类和接口
抽象类:
抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,
抽象类必须在类前用abstract关键字修饰。
因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。
抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,
那么等于白白创建了这个抽象类包含抽象方法的类称为抽象类,
但并不意味着抽象类中只能有抽象方法,它和普通类一样,
同样可以拥有成员变量和普通的成员方法。
注意,抽象类和普通类的主要有三点区别:
1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,
子类便无法实现该方法),缺省情况下默认为public。
2)抽象类不能用来创建对象;
3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。
如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
接口:
接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量
(并且只能是public static final变量,用private修饰会报编译错误),
而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,
比如private、protected、static、 final等修饰会报编译错误),
并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。
从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,
它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
区别:
1.语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。
2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
参考:深入理解Java的接口和抽象类
3、Java 中异常分为哪些种类
按 照 异 常 需 要 处 理 的 时 机 分 为 编 译 时 异 常 也 叫 CheckedException 和 运 行 时 异 常 也 叫RuntimeException。
Checked 异常处理方法有两种:
1 、当前方法知道如何处理该异常,则用 try...catch 块来处理该异常。
2 、当前方法不知道如何处理,则在定义该方法是声明抛出该异常。
4、题目
1 public int getNum(){
2 try {
3 int a = 1/0;
4 return 1;
5 } catch (Exception e) {
6 return 2;
7 }finally{
8 return 3;
9 }
10 }
代码在走到第 3 行的时候遇到了一个 MathException,这时第四行的代码就不会执行了,代码直接跳转到catch 语句中,走到第 6 行的时候,
异常机制有这么一个原则如果在 catch 中遇到了 return 或者异常等能使该函数终止的话那么用 finally 就必须先执行完 finally 代码块里面的代码然后再返回值。
因此代码又跳到第 8行,可惜第 8 行是一个 return 语句,那么这个时候方法就结束了,因此第 6 行的返回结果就无法被真正返回。
如果 finally 仅仅是处理了一个释放资源的操作,那么该道题最终返回的结果就是 2Java 的基本数据类型都有哪些各占几个字节?
Java 有 8 种基本数据类型
byte 1
char 2
sort 2
int 4
float 4
double 8
long 8
boolean 1(boolean 类型比较特别可能只占一个 bit,多个 boolean 可能共同占用一个字节)
String 是基本数据类型吗?可以被继承吗?
String 是引用类型,底层用 char 数组实现的。因为 String 是 final 类,在 java 中被 final 修饰的类不能被继承,
因此 String 当然不可以被继承。
java中的IO流
Java 中有几种类型的流
字节流和字符流。字节流继承于 InputStream 和 OutputStream,字符流继承于 InputStreamReader 和OutputStreamWriter。
字节流如何转为字符流
字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。
如何将一个 java 对象序列化到文件里?
在 java 中能够被序列化的类必须先实现 Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用。
Person model
package com.tan.test.modle;
import java.io.Serializable;
public class Person implements Serializable{
public String name;
public String sex;
public Person(String name, String sex) {
super();
this.name = name;
this.sex = sex;
}
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;
}
}
package com.tan.test.main;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.tan.test.modle.Person;
public class Test {
public static void main(String[] args) {
// 创建一个对象
Person people = new Person("张三", "男");
try {
// 实例化ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\person.txt"));
// 将对象写入文件
oos.writeObject(people);
oos.flush();
oos.close();
// 实例化ObjectInputStream对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\person.txt"));
try {
// 读取对象people,反序列化
Person p = (Person) ois.readObject();
System.out.println("姓名:" + p.getName());
System.out.println("性别:" + p.getSex());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileInputStream : 字节流是 以一个一个字节来读。
FileReader : 字符流是 以一个一个字符来读取的。
读取文件方法:
BufferedReader bufReader = null;
InputStreamReader isr = null;
FileReader fr = null;
try {
for(String fileName:fileNames){
方法一:
isr = new InputStreamReader(new FileInputStream("D:\test.txt"), "utf-8");
bufReader = new BufferedReader(isr);
方法二:
fr = new FileReader("D:\test.txt");
bufReader = new BufferedReader(fr);
//循环
while (bufReader.ready()) {
// 1. 得到每一行数据
String dataLine = bufReader.readLine();
}
}
HashMap 排序题,上机题
已知一个 HashMap
对 HashMap 的排序功能,该方法接收 HashMap
要求对 HashMap 中的 User 的 age 倒序进行排序。排序时 key=value 键值对不得拆散。
TIPS:要做出这道题必须对集合的体系结构非常的熟悉。HashMap 本身就是不可排序的,但是该道题偏偏让给
HashMap 排序,那我们就得想在 API 中有没有这样的 Map 结构是有序的,LinkedHashMap,对的,就是他,他是
Map 结构,也是链表结构,有序的,更可喜的是他是 HashMap 的子类,我们返回 LinkedHashMap
即可,还符合面向接口(父类编程的思想)。
但凡是对集合的操作,我们应该保持一个原则就是能用 JDK 中的 API 就有 JDK 中的 API,比如排序算法我们不应
该 去 用 冒 泡 或 者 选 择 , 而 是 首 先 想 到 用 Collections 集 合 工 具 类 。
HashMapTest.java
package com.tan.test.main;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import com.tan.test.modle.User;
public class HashMapTest {
public static void main(String[] args) {
HashMap users = new HashMap();
users.put(1, new User("张三", 25));
users.put(3, new User("李四", 22));
users.put(2, new User("王五", 28));
System.out.println(users);
HashMap sortHashMap = sortHashMap(users);
System.out.println(sortHashMap);
/**
* 控制台输出内容
* {1=User [name=张三, age=25], 2=User [name=王五, age=28], 3=User [name=李四,
age=22]}
{2=User [name=王五, age=28], 1=User [name=张三, age=25], 3=User [name=李四,
age=22]}
*/
}
public static HashMap sortHashMap(HashMap map) {
// 首先拿到 map 的键值对集合
Set> entrySet = map.entrySet();
// 将 set 集合转为 List 集合,为什么,为了使用工具类的排序方法
List> list = new ArrayList>(entrySet);
// 使用 Collections 集合工具类对 list 进行排序,排序规则使用匿名内部类来实现
Collections.sort(list, new Comparator>() {
@Override
public int compare(Entry o1, Entry o2) {
//按照要求根据 User 的 age 的倒序进行排
return o2.getValue().getAge()-o1.getValue().getAge();
}
});
//创建一个新的有序的 HashMap 子类的集合
LinkedHashMap linkedHashMap = new LinkedHashMap();
//将 List 中的数据存储在 LinkedHashMap 中
for(Entry entry : list){
linkedHashMap.put(entry.getKey(), entry.getValue());
}
//返回结果
return linkedHashMap;
}
}
package com.tan.test.modle;
import java.io.Serializable;
public class User implements Serializable{
public String name;
public int age;
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
集合的安全性问题
ArrayList、HashSet、HashMap 我们都看过这些集合的源码,每个方法都没有加锁,显然都是线程不安全的。
在集合中 Vector 和 HashTable 倒是线程安全的。
你打开源码会发现其实就是把各自核心方法添加上了synchronized 关键字。
Collections 工具类提供了相关的 API,可以让上面那 3 个不安全的集合变为安全的。
// Collections.synchronizedCollection(c)
// Collections.synchronizedList(list)
// Collections.synchronizedMap(m)
// Collections.synchronizedSet(s)
HashMap 和 HashTable 区别
从源码可以看出Hashtable 继承自 Dictiionary 而 HashMap继承自AbstractMap
Hashtable的put方法如下
public synchronized V put(K key, V value) { //###### 注意这里1
// Make sure the value is not null
if (value == null) { //###### 注意这里 2
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key.hashCode(); //###### 注意这里 3
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index]; e != null; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry e = tab[index];
tab[index] = new Entry(hash, key, value, e);
count++;
return null;
}
注意1 方法是同步的
注意2 方法不允许value==null
注意3 方法调用了key的hashCode方法,如果key==null,会抛出空指针异常
HashMap的put方法如下
public V put(K key, V value) { //###### 注意这里 1
if (key == null) //###### 注意这里 2
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i); //###### 注意这里
return null;
}
注意3 方法并没有对value进行任何调用,所以允许为null
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,
主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。
最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,
在多个线程访问Hashtable时,不需要自己为它的方法实现同步,
而HashMap 就必须为之提供外同步(Collections.synchronizedMap)。
StringBuffer和StringBuilder
StringBuilder是线程不安全的,运行效率高,如果一个字符串变量是在方法里面定义,
这种情况只可能有一个线程访问它,不存在不安全的因素了,则用StringBuilder。
如果要在类里面定义成员变量,并且这个类的实例对象会在多线程环境下使用
或者变量的内容不断变化,那么最好用StringBuffer。
1.如果要操作少量的数据用 = String
2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
ArrayList 和 LinkedList 的区别
ArrayList 采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,
所以最大的缺点就是插入删除时非常麻烦
LinkedList 采用的将对象存放在独立的空间中,而且在每个空间中还保存下一个链接的索引
但是缺点就是查找非常麻烦 要丛第一个索引开始
Java中HashMap和TreeMap的区别
HashMap:底层是哈希表数据结构。线程不同步。
TreeMap:底层是二叉树数据结构,线程不同步,可用于给Map集合中的键进行排序
java多线程的实现方法及区别?
Java中有两种实现多线程的方式。一是直接继承Thread类,二是实现Runnable接口。
我们可以通过编写一段代码来进行分析。我们用代码来模拟铁路售票系统,
实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程表示。
第一步这么写:
class ThreadTest extends Thread{
private int ticket = 100;
public void run(){
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +
"is saling ticket" + ticket--);
}else{
break;
}
}
}
}
public class ThreadDome1{
public static void main(String[] args){
ThreadTest t = new ThreadTest();
t.start();
t.start();
t.start();
t.start();
}
}
创建了一个线程对象,并重复启动四次,希望通过这种方式产生四个线 程。
从运行的结果来看我们发现其实只有一个线程在运行,这个结果告诉我们:
一个线程对象只能启动一个线程,无论你调用多少遍start()方法,结果只有 一个线程。
第二步,改进:
public class ThreadDemo1{
public static void main(String[] args){
new ThreadTest().start();
new ThreadTest().start();
new ThreadTest().start();
new ThreadTest().start();
}
}
从结果上看每个票号都被打印了四次,即四个线程各自卖各自的100张票,
而不去卖共同的100张票。这种情况是怎么造成的呢?我们需要的是,
多个线程去处 理同一个资源,一个资源只能对应一个对象,在上面的程序中,
我们创建了四个ThreadTest对象,就等于创建了四个资源,每个资源都有100张票,
每 个线程都在独自处理各自的资源。
经过这些实验和分析,可以总结出,要实现这个铁路售票程序,
我们只能创建一个资源对象,但要创建多个线程去处理同一个资源对象,
并且每个线程上所运行的是相同的程序代码。在回顾一下使用接口编写多线程的过程。
class ThreadTest implements Runnable{
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
System.out.println(Thread.currentThread().getName() +
" is saling ticket " + tickets--);
}
}
}
}
public class ThreadDemo1{
public static void main(String[] args){
ThreadTest t = new ThreadTest();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
上面的程序中,创建了四个线程,每个线程调用的是同一个ThreadTest对象中的run()方法,
访问的是同一个对象中的变量 (tickets)的实例,这个程序满足了我们的需求。
可见,实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,
数据有效的分离,较好地体现了面向对象的设计思想。
(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,
即当我们要将已经继承了某一个类的子类放入多线程中,
由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,
那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。
当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。
多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,
即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象
作为构造函数 实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
在 java 中 wait 和 sleep 方法的不同?
sleep是Thread类的一个方法,wait是Object类的一个方法。
sleep不会释放资源,wait会释放掉资源。
sleep是线程被调用时,占着cpu去睡觉,其他线程不能占用cpu,os认为该线程正在工作,不会让出系统资源,wait是进入等待池等待,让出系统资源,其他线程可以占用cpu,一般wait不会加时间限制,因为如果wait的线程运行资源不够,再出来也没用,要等待其他线程调用notifyall方法唤醒等待池中的所有线程,才会在进入就绪序列等待os分配系统资源,
sleep是静态方法,是谁掉的谁去睡觉,就算是在main线程里调用了线程b的sleep方法,实际上还是main去睡觉,想让线程b去睡觉要在b的代码中掉sleep
sleep(100L)是占用cpu,线程休眠100毫秒,其他进程不能再占用cpu资源,wait(100L)是进入等待池中等待,交出cpu等系统资源供其他进程使用,在这100毫秒中,该线程可以被其他线程notify,但不同的是其他在等待池中的线程不被notify不会出来,但这个线程在等待100毫秒后会自动进入就绪队列等待系统分配资源,换句话说,sleep(100)在100毫秒后肯定会运行,但wait在100毫秒后还有等待os调用分配资源,所以wait100的停止运行时间是不确定的,但至少是100毫秒。
synchronized 和 volatile 关键字的作用
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是
立即可见的。
2)禁止进行指令重排序。
volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
1.
volatile 仅能使用在变量级别;
synchronized 则可以使用在变量、方法、和类级别的
2.
volatile 仅能实现变量的修改可见性,并不能保证原子性;
synchronized 则可以保证变量的修改可见性和原子性
3.
volatile 不会造成线程的阻塞;
synchronized 可能会造成线程的阻塞。
4.
volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化
Java中堆内存和栈内存的区别
在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配
堆内存用于存放由new创建的对象和数组
栈主要是用来执行程序的.堆主要用来存放对象的。
静态内部类和非静态内部类之间到底有什么不同呢?
java中的类可以是static吗?答案是可以。在java中我们可以有静态实例变量、静态方法、静态块。类也可以是静态的。
java允许我们在一个类里面定义静态类。比如内部类(nested class)。把nested class封闭起来的类叫外部类。在java中,我们不能用static修饰顶级类(top level class)。只有内部类可以为static。
下面是两者间主要的不同
(1)内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。
(2)非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。
(3)一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
/* 下面程序演示如何在java中创建静态内部类和非静态内部类 */
class OuterClass{
private static String msg = "GeeksForGeeks";
// 静态内部类
public static class NestedStaticClass{
// 静态内部类只能访问外部类的静态成员
public void printMessage() {
// 试着将msg改成非静态的,这将导致编译错误
System.out.println("Message from nested static class: " + msg);
}
}
// 非静态内部类
public class InnerClass{
// 不管是静态方法还是非静态方法都可以在非静态内部类中访问
public void display(){
System.out.println("Message from non-static nested class: "+ msg);
}
}
}
class Main
{
// 怎么创建静态内部类和非静态内部类的实例
public static void main(String args[]){
// 创建静态内部类的实例
OuterClass.NestedStaticClass printer = new OuterClass.NestedStaticClass();
// 创建静态内部类的非静态方法
printer.printMessage();
// 为了创建非静态内部类,我们需要外部类的实例
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
// 调用非静态内部类的非静态方法
inner.display();
// 我们也可以结合以上步骤,一步创建的内部类实例
OuterClass.InnerClass innerObject = new OuterClass().new InnerClass();
// 同样我们现在可以调用内部类方法
innerObject.display();
}
}